package org.xliu.cs.projects.anno_for_doc.generate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xliu.cs.projects.anno_for_doc.annotations.ClassNote;
import org.xliu.cs.projects.anno_for_doc.annotations.IgnoreNote;
import org.xliu.cs.projects.anno_for_doc.annotations.MethodNote;
import org.xliu.cs.projects.anno_for_doc.annotations.PkgNote;

import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

@ClassNote("根据注解生成文档")
public abstract class AbstractGenerator implements Generator {
    private static final Logger log = LoggerFactory.getLogger(AbstractGenerator.class);

    /**
     * Get ClassLoader for reflection class.
     */
    abstract protected ClassLoader getClassLoader();

    /**
     * 扫描包下的类文件（不支持 jar），根据注解生成 markdown，保证 code / doc 一致性。
     *
     * @param pkgPrefix   : 包前缀，用于过滤，如 "org.xliu.cs.algs_ds"
     * @param outFilePath : 输出文件名，如 "README.md"
     * @param skipPkgs    : 忽略的包名，如 "org/xliu/cs/algs_ds/ignore_package"
     */
    public void generate(String pkgPrefix, String outFilePath, Set<String> skipPkgs) throws IOException {
        // use specific classloader to get class.
        ClassLoader classLoader = getClassLoader();

        // 包名转换为文件路径的形式，因为要扫描文件
        String pkgPrefixFileFormat = pkgPrefix.replace(".", "/");
        Enumeration<URL> dirs = classLoader.getResources(pkgPrefixFileFormat);

        List<String> classes = new ArrayList<>();
        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();
            log.debug("handle file {}", url);
            if (url.getProtocol().equals("file")) {
                classes.addAll(listClasses(new File(url.getFile()), pkgPrefixFileFormat));
            }
        }
        // 获取所有的 package-info，获取其包名，和包下的所有类
        SortedMap<String, PkgAndClasses> pkgClasses = groupByPackage(classes, skipPkgs);
        int rootLevel = getRootLevel(pkgClasses.keySet());

        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
                Files.newOutputStream(Paths.get(outFilePath)), StandardCharsets.UTF_8))) {
            for (Map.Entry<String, PkgAndClasses> pkgClass : pkgClasses.entrySet()) {
                handlePkg(pkgClass.getValue(), rootLevel, writer, classLoader);
                handleClasses(pkgClass.getValue(), writer, classLoader);
            }
        }
    }

    static int getRootLevel(Set<String> pkgs) {
        Optional<Integer> min = pkgs.stream().map(s -> s.split("\\.").length).min(Integer::compareTo);
        return min.orElse(0);
    }

    // 获取包的层次，如 org.xliu.cs 的包层次为 3
    private int getPkgLevel(String pkgName) {
        return pkgName.split("\\.").length;
    }

    private SortedMap<String, PkgAndClasses> groupByPackage(List<String> classes, Set<String> skipPkgs) {
        SortedMap<String, PkgAndClasses> pkgClasses = new TreeMap<>();
        for (String clazz : classes) {
            // 获取包名
            int index = clazz.lastIndexOf(".");
            String pkg;
            if (index != -1) {
                pkg = clazz.substring(0, index);
            } else {
                // 不存在包名
                pkg = "";
            }

            // 过滤掉不需要匹配的包名
            if (skipPkgs.contains(pkg)) {
                continue;
            }

            pkgClasses.compute(pkg, (k, v) -> {
                if (v == null) {
                    v = new PkgAndClasses(pkg);
                }
                // 忽略包名类的类注解
                if (!clazz.endsWith(".package-info")) {
                    v.classes.add(clazz);
                }

                return v;
            });
        }
        return pkgClasses;
    }

    private void handlePkg(PkgAndClasses pkgAndClasses, int rootLevel, Writer writer, ClassLoader classLoader) throws IOException {
        String pkg = pkgAndClasses.pkgName;
        String pkgClass = String.format("%s.package-info", pkg);
        try {
            // use specific classloader to get class
            Class<?> aClass = classLoader.loadClass(pkgClass);
            PkgNote pkgNote = aClass.getAnnotation(PkgNote.class);

            if (pkgNote == null) {
                if (aClass.getAnnotation(IgnoreNote.class) == null) {
                    log.error("package [{}] has no pkg annotation.", pkg);
                }
                return;
            }
            log.debug("handle pkg [{}]", aClass.getPackage().getName());
            int level = getPkgLevel(pkg) - rootLevel;
            // # 重复 N 次
            String repeat = new String(new char[level]).replace("\0", "#");

            writer.write(String.format("#%s %s\n", repeat, pkgNote.value()));
            writer.write(pkgNote.description() + "\n\n");
        } catch (ClassNotFoundException e) {
            // ignore not found
            log.error("package [{}] has no pkg annotation.", pkg);
        }
    }

    private void handleClasses(PkgAndClasses pkgAndClasses, Writer writer, ClassLoader classLoader) throws IOException {
        for (String clazz : pkgAndClasses.classes) {
            // 根据名称加载类
            Class<?> aClass = null;
            try {
                aClass = classLoader.loadClass(clazz);
            } catch (ClassNotFoundException e) {
                log.error("class [{}] is not exist", clazz);
                continue;
            }

            // use specific classloader to get class
            ClassNote classNote = aClass.getAnnotation(ClassNote.class);
            if (classNote == null) {
                // 忽略内部类和显式标记不需要注解的类
                if (!aClass.getName().contains("$") && aClass.getAnnotation(IgnoreNote.class) == null) {
                    log.error("class [{}] has no class annotation.", clazz);
                }
                continue;
            }
            writer.write(String.format("[%s](%s)\n\n", classNote.value(), "src/main/java/" + aClass.getName().replace(".", "/") + ".java"));
            List<Method> sortedMethods = Arrays.stream(aClass.getMethods()).sorted(
                    Comparator.comparing(Method::getName)
            ).collect(Collectors.toList());

            for (Method method : sortedMethods) {
                MethodNote methodNote = method.getAnnotation(MethodNote.class);
                if (methodNote == null) {
//                    log.error("method [{}] has no method annotation.", method.getName());
                    continue;
                }
                writer.write(String.format("- %s\n\n", methodNote.value()));
            }
        }

    }

    private List<String> listClasses(File file, String pkgPrefix) {
        List<String> classes = new ArrayList<>();
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    classes.addAll(listClasses(f, pkgPrefix));
                }
            }
        } else {
            if (file.getName().endsWith(".class")) {
                // windows 下的路径兼容，将 '\' 换成 '/'
                String fPath = file.getAbsolutePath().replaceAll("\\\\", "/");
                String className = fPath.substring(fPath.lastIndexOf(pkgPrefix))
                        .replace(".class", "").replaceAll("/", ".");
                classes.add(className);
            }
        }
        return classes;
    }

    private static class PkgAndClasses {
        String pkgName;
        List<String> classes = new ArrayList<>();

        public PkgAndClasses(String pkg) {
            pkgName = pkg;
        }

    }
}
